﻿#include "../../src/box/service_box.hh"
#include "../../src/detail/box_config_impl.hh"
#include "../../src/repo/src/include/example/protobuf/example.service.pb.h"
#include "../../src/util/lua/lua_debug.hh"
#include "../../src/util/lua/lua_helper.hh"
#include "../../src/util/os_util.hh"
#include "../../src/util/time_util.hh"
#include "../framework/unittest.hh"

#include <cmath>
#include <filesystem>
#include <fstream>

FIXTURE_BEGIN(test_lua_helper)

using namespace kratos::lua;

lua_State *global_lua{nullptr};

void update_for(std::time_t duration, kratos::service::ServiceBox &sb) {
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    sb.update(now);
    if (now - start > duration) {
      break;
    } else {
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
  }
}

void update_for(std::time_t duration, kratos::service::ServiceBox &sb1,
                kratos::service::ServiceBox &sb2) {
  auto start = kratos::util::get_os_time_millionsecond();
  while (true) {
    auto now = kratos::util::get_os_time_millionsecond();
    sb1.update(now);
    sb2.update(now);
    if (now - start > duration) {
      break;
    } else {
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
  }
}

SETUP([]() {
  std::ofstream ofs1;
  ofs1.open("test1.lua", std::ios::out | std::ios::trunc);
  static const char *test1_lua = R"(
    function test1()
    end
  )";
  ofs1 << test1_lua;
  ofs1.close();

  std::ofstream ofs2;
  ofs2.open("test2.lua", std::ios::out | std::ios::trunc);
  static const char *test2_lua = R"(
    function test2()
       assert(nil)
    end
  )";
  ofs2 << test2_lua;
  ofs2.close();

  std::ofstream ofs3;
  ofs3.open("test3.lua", std::ios::out | std::ios::trunc);
  static const char *test3_lua = R"(
    function test3(t)
       return t
    end
  )";
  ofs3 << test3_lua;
  ofs3.close();

  std::ofstream ofs4;
  ofs4.open("test4.lua", std::ios::out | std::ios::trunc);
  static const char *test4_lua = R"(
    function test4()
       a = 1
       a[1] = 2
    end
  )";
  ofs4 << test4_lua;
  ofs4.close();

  std::ofstream ofs5;
  ofs5.open("test5.lua", std::ios::out | std::ios::trunc);
  static const char *test5_lua = R"(
    function test5()
       coroutine.yield()
    end
  )";
  ofs5 << test5_lua;
  ofs5.close();

  std::ofstream ofs6;
  ofs6.open("test6.lua", std::ios::out | std::ios::trunc);
  static const char *test6_lua = R"(
    function test6()
       coroutine.yield()
       return 1
    end
  )";
  ofs6 << test6_lua;
  ofs6.close();

  std::ofstream ofs7;
  ofs7.open("test7.lua", std::ios::out | std::ios::trunc);
  static const char *test7_lua = R"(
    function test7()
       return coroutine.yield()
    end
  )";
  ofs7 << test7_lua;
  ofs7.close();

  std::ofstream ofs8;
  ofs8.open("test8.lua", std::ios::out | std::ios::trunc);
  static const char *test8_lua = R"(
    function test8(t)
      assert(t._1)
      assert(t._3)
      assert(t._4)
      assert(t._5)
      assert(t._6)
      assert(t._7)
      assert(t._8)
      assert(t._9)
      assert(t._10)
      assert(t._11)
      assert(t._12)
      assert(t._13.field1)
      assert(t._13.field2)
      assert(t._13.field3[1])
      assert(t._13.field3[2])
      assert(t._13.field4[1])
      assert(t._13.field4[2])
      assert(t._13.field5[1])
      assert(t._13.field5[2])
      assert(t._13.field6)
      assert(t._13.field7)
      assert(t._13.field8)
    end
  )";
  ofs8 << test8_lua;
  ofs8.close();

  std::ofstream ofs9;
  ofs9.open("test9.lua", std::ios::out | std::ios::trunc);
  static const char *test9_lua = R"(
    function test9(t)
      return t
    end
  )";
  ofs9 << test9_lua;
  ofs9.close();

  std::ofstream ofs10;
  ofs10.open("test10.lua", std::ios::out | std::ios::trunc);
  static const char *test10_lua = R"(
    function test10(t)
      coroutine.yield()
    end
  )";
  ofs10 << test10_lua;
  ofs10.close();

  kratos::util::make_dir("5871407833380299500_lua");
  kratos::util::make_dir("5871407833380299500_lua/json");
  kratos::util::make_dir("5871407833380299500_lua/proto");
  kratos::util::make_dir("5871407833380299500_lua/script");
  kratos::util::make_dir("5871407833380299500_lua/stub");

  kratos::util::make_dir("5871407833815435533_lua");
  kratos::util::make_dir("5871407833815435533_lua/json");
  kratos::util::make_dir("5871407833815435533_lua/proto");
  kratos::util::make_dir("5871407833815435533_lua/script");
  kratos::util::make_dir("5871407833815435533_lua/stub");

  kratos::util::make_dir("lua_proxy");

  try {
    std::filesystem::copy_file(
        "../../../src/repo/src/idl/example.idl.cpp.json",
        "5871407833815435533_lua/json/example.idl.cpp.json",
        std::filesystem::copy_options::overwrite_existing);
    std::filesystem::copy_file(
        "../../../src/repo/src/idl/example.service.proto",
        "5871407833815435533_lua/proto/example.service.proto",
        std::filesystem::copy_options::overwrite_existing);
    std::filesystem::copy_file(
        "../../../src/repo/src/idl/example.idl.cpp.json",
        "5871407833380299500_lua/json/example.idl.cpp.json",
        std::filesystem::copy_options::overwrite_existing);
    std::filesystem::copy_file(
        "../../../src/repo/src/idl/example.service.proto",
        "5871407833380299500_lua/proto/example.service.proto",
        std::filesystem::copy_options::overwrite_existing);
  } catch (std::exception &e) {
    std::cerr << e.what() << std::endl;
  }

  std::ofstream proxy_ofs;
  proxy_ofs.open("lua_proxy/proxy.lua", std::ios::out | std::ios::trunc);
  static const char *proxy_src = R"(
    ProxyLuaLogin = {}
    ProxyLuaLogin.proxy_id = 0

    function ProxyLuaLogin:login(arg1, arg2)
      local args = {}
      args._1 = arg1
      args._2 = arg2
      local is_yield = kratos_call_proxy(self.service_id, 1, self.proxy_id, args)
      local service_id, ret = coroutine.yield()
      if ret == nil or service_id == nil then
        return nil
      end
      self.service_id = service_id
      return ret._1
    end

    function ProxyLuaLogin:logout(arg1)
      local args = {}
      args._1 = arg1
      kratos_call_proxy(self.service_id, 2, self.proxy_id, args)
      service_id = coroutine.yield()
      self.service_id = service_id
      return {}
    end

    function ProxyLuaLogin:oneway_method(args)
      kratos_call_proxy(self.service_id, 3, self.proxy_id, {})
      return {}
    end

    function ProxyLuaLogin:normal_method(args)
      kratos_call_proxy(self.service_id, 4, self.proxy_id, {})
      service_id = coroutine.yield()
      self.service_id = service_id
      return {}
    end

    Proxy = {}

    function Proxy:get_LuaLogin_timeout(name, timeout)      
      local proxy = ctx:get_proxy_timeout(name, 5871407833815435533, timeout)
      if proxy == nil then
        return nil
      end
      local proxy_table = ctx:clone(ProxyLuaLogin)
      proxy_table.proxy_id = proxy.proxy_id
      proxy_table.service_uuid = proxy.service_uuid
      proxy_table.service_name = proxy.service_name
      proxy_table.service_id = 0
      return proxy_table
    end

    function Proxy:get_LuaLogin_from_peer()
      local proxy = ctx:get_proxy_from_peer()
      local proxy_table = ctx:clone(ProxyLuaLogin)
      proxy_table.proxy_id = proxy.proxy_id
      proxy_table.service_uuid = proxy.service_uuid
      proxy_table.service_id = 0
      return proxy_table
    end

    function Proxy:get_LuaLogin_from_transport()
      local proxy = ctx:get_proxy_from_transport()
      local proxy_table = ctx:clone(ProxyLuaLogin)
      proxy_table.proxy_id = proxy.proxy_id
      proxy_table.service_uuid = proxy.service_uuid
      proxy_table.service_id = 0
      return proxy_table
    end

  )";
  proxy_ofs << proxy_src;
  proxy_ofs.close();

  std::ofstream box_config_ofs1;
  box_config_ofs1.open("box1.cfg", std::ios::out | std::ios::trunc);
  static const char *box1_cfg = R"(
    listener = {
        host = ("127.0.0.1:7888")
    }
    service_finder = {
        type = "zookeeper"
        hosts = "127.0.0.1:2181"
    }
    box_channel_recv_buffer_len = 102400
    box_name = "service_box"
    logger_config_line = "console://;line=[%y%m%d-%H:%M:%S:%U];flush=true;mode=day"
    connect_other_box_timeout = 2000
    service_finder_connect_timeout = 2000
    necessary_service = ()
    open_coroutine = "true"
    is_open_time_system = "true"
    service = {
        service_dir="../../../src/repo/lib/stub/example/LuaLogin/Debug"
        preload_service=<5871407833815435533:"libLuaLogin.so">
    }

    test = {
        array = ("1", "2", "3")
        table = <1:"1", 2:"2">
        integer = 1
        string = "hello"
    }
  )";
  box_config_ofs1 << box1_cfg;
  box_config_ofs1.close();

  std::ofstream box_config_ofs2;
  box_config_ofs2.open("box2.cfg", std::ios::out | std::ios::trunc);
  static const char *box2_cfg = R"(
    listener = {
        host = ("127.0.0.1:6888")
    }
    service_finder = {
        type = "zookeeper"
        hosts = "127.0.0.1:2181"
    }
    box_channel_recv_buffer_len = 102400
    box_name = "service_box"
    logger_config_line = "console://;line=[%y%m%d-%H:%M:%S:%U];flush=true;mode=day"
    connect_other_box_timeout = 2000
    service_finder_connect_timeout = 2000
    necessary_service = ()
    open_coroutine = "true"
    service = {
        service_dir="../../../src/repo/lib/stub/example/Lua/Debug"
        preload_service=<5871407833380299500:"libLua.so">
    }
  )";
  box_config_ofs2 << box2_cfg;
  box_config_ofs2.close();
})

bool check_and_setup(const char *file_path) {
  if (global_lua) {
    lua_close(global_lua);
  }
  global_lua = luaL_newstate();
  luaL_openlibs(global_lua);
  auto error = luaL_loadfile(global_lua, file_path);
  if (error) {
    return false;
  }
  std::string error_string;
  return LuaUtil::catch_lua_error(global_lua, lua_pcall(global_lua, 0, 0, 0),
                                  error_string);
}

CASE(TestCallNoRet1) {
  ASSERT_TRUE(check_and_setup("test1.lua"));
  std::string error;
  ASSERT_TRUE(LuaUtil::call_lua_function_no_ret("test1", global_lua, error));
  ASSERT_TRUE(error.empty());
}

CASE(TestCallNoRet2) {
  ASSERT_TRUE(check_and_setup("test2.lua"));
  std::string error;
  ASSERT_TRUE(!LuaUtil::call_lua_function_no_ret("test2", global_lua, error));
  ASSERT_TRUE(!error.empty());
  ASSERT_TRUE(LuaUtil::get_status(global_lua) == ThreadState::DEAD);
}

CASE(TestCallNoRet3) {
  ASSERT_TRUE(check_and_setup("test2.lua"));
  std::string error;
  ASSERT_TRUE(!LuaUtil::call_lua_function_no_ret("unknown", global_lua, error));
  ASSERT_TRUE(!error.empty());
}

CASE(TestCall1) {
  ASSERT_TRUE(check_and_setup("test3.lua"));
  std::string error;
  int ret = 0;
  ASSERT_TRUE(LuaUtil::call_lua_function("test3", global_lua, ret, error, 100));
  ASSERT_TRUE(error.empty());
  ASSERT_TRUE(ret == 100);
}

CASE(TestCall2) {
  ASSERT_TRUE(check_and_setup("test4.lua"));
  std::string error;
  int ret = 0;
  ASSERT_TRUE(
      !LuaUtil::call_lua_function("test4", global_lua, ret, error, 100));
  ASSERT_TRUE(!error.empty());
  ASSERT_TRUE(ret != 100);
}

CASE(TestCall3) {
  ASSERT_TRUE(check_and_setup("test4.lua"));
  std::string error;
  int ret = 0;
  ASSERT_TRUE(
      !LuaUtil::call_lua_function("unknown", global_lua, ret, error, 100));
  ASSERT_TRUE(!error.empty());
  ASSERT_TRUE(ret != 100);
}

CASE(TestYield1) {
  ASSERT_TRUE(check_and_setup("test5.lua"));
  std::string error;
  ASSERT_TRUE(LuaUtil::call_lua_function_no_ret("test5", global_lua, error));
  ASSERT_TRUE(error.empty());
}

CASE(TestYield2) {
  ASSERT_TRUE(check_and_setup("test6.lua"));
  std::string error;
  ASSERT_TRUE(LuaUtil::call_lua_function_no_ret("test6", global_lua, error));
  ASSERT_TRUE(error.empty());
  lua_resume(global_lua, nullptr, 0);
  ASSERT_TRUE(lua_isinteger(global_lua, -1));
}

CASE(TestResume1) {
  ASSERT_TRUE(check_and_setup("test7.lua"));
  std::string error;
  ASSERT_TRUE(LuaUtil::call_lua_function_no_ret("test7", global_lua, error));
  ASSERT_TRUE(error.empty());
  LuaUtil::lua_push(global_lua, 1);
  LuaUtil::resume(global_lua, error, 1);
  ASSERT_TRUE(lua_isinteger(global_lua, -1));
  ASSERT_TRUE(1 == (int)lua_tointeger(global_lua, -1));
}

CASE(TestResume2) {
  ASSERT_TRUE(check_and_setup("test7.lua"));
  std::string error;
  ASSERT_TRUE(LuaUtil::call_lua_function_no_ret("test7", global_lua, error));
  ASSERT_TRUE(error.empty());
  LuaUtil::lua_push(global_lua, 1);
  ASSERT_TRUE(LuaUtil::resume(global_lua, error, 1));
  ASSERT_TRUE(error.empty());
  ASSERT_TRUE(lua_isinteger(global_lua, -1));
  ASSERT_TRUE(1 == (int)lua_tointeger(global_lua, -1));
  ASSERT_TRUE(!LuaUtil::resume(global_lua, error));
  ASSERT_TRUE(!error.empty());
}

CASE(TestPbToTable1) {
  ASSERT_TRUE(check_and_setup("test8.lua"));
  example::MyService_method18_args args18;
  args18.set__1(1);
  args18.set__2(2);
  args18.set__3(3);
  args18.set__4(4);
  args18.set__5(5);
  args18.set__6(6);
  args18.set__7(7);
  args18.set__8(8);
  args18.set__9(true);
  args18.set__10("10");
  args18.set__11(11.0f);
  args18.set__12(12.0f);
  args18.mutable__13()->set_field1(1);
  args18.mutable__13()->set_field2("2");
  args18.mutable__13()->add_field3("3");
  args18.mutable__13()->add_field3("4");
  args18.mutable__13()->add_field4("5");
  args18.mutable__13()->add_field4("6");
  args18.mutable__13()->mutable_field5()->insert({1, "1"});
  args18.mutable__13()->mutable_field5()->insert({2, "2"});
  args18.mutable__13()->set_field6(true);
  args18.mutable__13()->set_field7(7.0f);
  args18.mutable__13()->set_field8(8.0f);
  std::string error;
  ASSERT_TRUE(
      LuaUtil::call_lua_function_no_ret("test8", global_lua, error, args18));
  ASSERT_TRUE(error.empty());
}

CASE(TestTableToPb1) {
  ASSERT_TRUE(check_and_setup("test9.lua"));
  example::MyService_method18_args args18;
  args18.set__1(1);
  args18.set__2(2);
  args18.set__3(3);
  args18.set__4(4);
  args18.set__5(5);
  args18.set__6(6);
  args18.set__7(7);
  args18.set__8(8);
  args18.set__9(true);
  args18.set__10("10");
  args18.set__11(11.0f);
  args18.set__12(12.0f);
  args18.mutable__13()->set_field1(1);
  args18.mutable__13()->set_field2("2");
  args18.mutable__13()->add_field3("3");
  args18.mutable__13()->add_field3("4");
  args18.mutable__13()->add_field4("5");
  args18.mutable__13()->add_field4("6");
  args18.mutable__13()->mutable_field5()->insert({1, "1"});
  args18.mutable__13()->mutable_field5()->insert({2, "2"});
  args18.mutable__13()->set_field6(true);
  args18.mutable__13()->set_field7(7.0f);
  args18.mutable__13()->set_field8(8.0f);
  std::string error;
  example::MyService_method18_args ret;
  ASSERT_TRUE(
      LuaUtil::call_lua_function("test9", global_lua, ret, error, args18));
  ASSERT_TRUE(error.empty());

  ASSERT_TRUE(ret._1() == 1);
  ASSERT_TRUE(ret._2() == 2);
  ASSERT_TRUE(ret._3() == 3);
  ASSERT_TRUE(ret._4() == 4);
  ASSERT_TRUE(ret._5() == 5);
  ASSERT_TRUE(ret._6() == 6);
  ASSERT_TRUE(ret._7() == 7);
  ASSERT_TRUE(ret._8() == 8);
  ASSERT_TRUE(ret._9() == true);
  ASSERT_TRUE(ret._10() == "10");
  ASSERT_TRUE(std::fabs(ret._11() - 11.0f) < 0.001f);
  ASSERT_TRUE(std::fabs(ret._12() - 12.0f) < 0.001f);
  ASSERT_TRUE(ret._13().field1() == 1);
  ASSERT_TRUE(ret._13().field2() == "2");
  ASSERT_TRUE(ret._13().field3().at(0) == "3");
  ASSERT_TRUE(ret._13().field3().at(1) == "4");
  ASSERT_TRUE(ret._13().field4().at(0) == "5");
  ASSERT_TRUE(ret._13().field4().at(1) == "6");
  ASSERT_TRUE(ret._13().field5().at(1) == "1");
  ASSERT_TRUE(ret._13().field5().at(2) == "2");
  ASSERT_TRUE(ret._13().field6() == true);
  ASSERT_TRUE(std::fabs(ret._13().field7() - 7.0f) < 0.001f);
  ASSERT_TRUE(std::fabs(ret._13().field8() - 8.0f) < 0.001f);
}

CASE(TestLuaService1) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function timer_cb(id)
      print("timeout")
      return true
    end
    function on_after_fork()
      print("on_after_fork")
      timer_id1 = kratos_start_timer(1000, timer_cb)
      print("timer_id:"..timer_id1)
      timer_id2 = kratos_start_timer(1000,
                  function(id) 
                    assert(nil)
                  end)
      print("timer_id2:"..timer_id2)
      return true
    end

    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  std::this_thread::sleep_for(std::chrono::seconds(1));
  sb.update(kratos::util::get_os_time_millionsecond());
  sb.stop();
}

CASE(TestLuaService2) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      timer_id = kratos_start_timer(500,
        function(id)
           kratos_sleep(100)
           print("timer back")
        end)
      print("timer_id:"..timer_id)
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  update_for(2000, sb);
  sb.stop();
}

CASE(TestLuaService3) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      timer_id = ctx:expire_at(500,
        function(id)
           ctx:sleep(100)
           print("timer back")
        end)
      print("timer_id:"..timer_id)
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  update_for(2000, sb);
  sb.stop();
}

CASE(TestLuaService4) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      ctx:expire_at(100,
        function(id)
           -- yield并在500毫秒后返回并检查是否获取到代理
           proxy = ctx:get_proxy_timeout("abc", 111, 500)
           print("get proxy timeout")
        end
      )
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  update_for(2000, sb);
  sb.stop();
}

CASE(TestLuaService5) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      local id = ctx:expire_at(100,
        function(id)
           print("timeout")
        end
      )
      ctx:cancel(id)
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  update_for(2000, sb);
  sb.stop();
}

CASE(TestLuaService6) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      local id = ctx:expire_periodic(500,
        function(id)
           print("timeout")
           return true
        end
      )
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb;
  const char *argv[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb.start(2, argv));
  update_for(2000, sb);
  sb.stop();
}

CASE(TestLuaService7) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    local test_service = nil
    function on_tick(now)
      if test_service ~= nil then
        print("call service result:", test_service:login("user", "pass"))
      end 
    end
    function on_after_fork()
      print("on_after_fork")
      ctx:expire_periodic(100,
        function(id)
           test_service = Proxy:get_LuaLogin_timeout("test_service", 100)
           if test_service ~= nil then
             return false
           end
        end
      )
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  std::ofstream ofs1;
  ofs1.open("5871407833815435533_lua/script/main.lua",
            std::ios::out | std::ios::trunc);
  static const char *test_service_login_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      ctx:register_service("test_service")
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end

    StubLogin = {}
    function StubLogin:login(name, pass)
      return 1
    end

    function StubLogin:oneway_method()
    end

    function StubLogin:normal_method()
    end
  )";
  ofs1 << test_service_login_lua;
  ofs1.close();

  std::ofstream ofs_stub;
  ofs_stub.open("5871407833815435533_lua/stub/stub.lua",
                std::ios::out | std::ios::trunc);
  static const char *test_lua_styb = R"(
    function _5871407833815435533_login(args)
      local ret_table = {}
      ret_table._1 = StubLogin:login(args._1, args._2)
      return ret_table
    end

    function _5871407833815435533_oneway_method()
      StubLogin:oneway_method()
    end

    function _5871407833815435533_normal_method()
      StubLogin:oneway_method()
    end
  )";
  ofs_stub << test_lua_styb;
  ofs_stub.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));

  update_for(1000, sb1);

  kratos::service::ServiceBox sb2;
  const char *argv2[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb2.start(2, argv2));

  update_for(1000, sb1, sb2);

  sb1.stop();
  sb2.stop();
}

CASE(TestLuaService8) {
  std::ofstream ofs;
  ofs.open("5871407833380299500_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    local test_service = nil
    function on_tick(now)
      if test_service ~= nil then
        test_service:oneway_method()
        test_service:normal_method()
        print("call back")
      end 
    end
    function on_after_fork()
      print("on_after_fork")
      ctx:expire_periodic(100,
        function(id)
           test_service = Proxy:get_LuaLogin_timeout("test_service", 100)
           if test_service ~= nil then
             return false
           end
        end
      )
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  std::ofstream ofs1;
  ofs1.open("5871407833815435533_lua/script/main.lua",
            std::ios::out | std::ios::trunc);
  static const char *test_service_login_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      ctx:register_service("test_service")
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end

    StubLogin = {}
    function StubLogin:login(name, pass)
      return 1
    end

    function StubLogin:oneway_method()
    end

    function StubLogin:normal_method()
    end
  )";
  ofs1 << test_service_login_lua;
  ofs1.close();

  std::ofstream ofs_stub;
  ofs_stub.open("5871407833815435533_lua/stub/stub.lua",
                std::ios::out | std::ios::trunc);
  static const char *test_lua_styb = R"(
    function _5871407833815435533_login(args)
      local ret_table = {}
      ret_table._1 = StubLogin:login(args._1, args._2)
      return ret_table
    end

    function _5871407833815435533_oneway_method()
      StubLogin:oneway_method()
    end

    function _5871407833815435533_normal_method()
      StubLogin:oneway_method()
    end
  )";
  ofs_stub << test_lua_styb;
  ofs_stub.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));

  update_for(1000, sb1);

  kratos::service::ServiceBox sb2;
  const char *argv2[] = {"test", "--config=box2.cfg"};
  ASSERT_TRUE(sb2.start(2, argv2));

  update_for(1000, sb1, sb2);

  sb1.stop();
  sb2.stop();
}

CASE(TestLuaArgument1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_after_fork()
      print("on_after_fork")
      print(ctx.argument:get_config_file_path())
      print(ctx.argument:get_config_file_name())
      print(ctx.argument:get_max_frame())
      print(ctx.argument:is_daemon())
      print(ctx.argument:get_config_center_api_url())
      print(ctx.argument:is_open_system_exception())
      ctx:log_verb("verb")
      ctx:log_info("info")
      ctx:log_debug("debug")
      ctx:log_warn("warn")
      ctx:log_except("except")
      ctx:log_fail("fail")
      ctx:log_fatal("fatal")
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  sb1.stop();
}

CASE(TestLuaConfig1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function on_tick(now)
    end
    function on_reload()
      print("reloaded")
    end
    function on_after_fork()
      print("on_after_fork")
      print_r(ctx.config:get_listener_list())
      print(ctx.config:get_service_finder_type())
      print(ctx.config:get_service_finder_hosts())
      print(ctx.config:get_service_finder_connect_timeout())
      print_r(ctx.config:get_necessary_service())
      print(ctx.config:get_connect_other_box_timeout())
      print(ctx.config:get_box_channel_recv_buffer_len())
      print(ctx.config:get_box_name())
      print(ctx.config:get_logger_config_line())
      print(ctx.config:get_service_dir())
      print_r(ctx.config:get_preload_service())
      print(ctx.config:is_open_coroutine())
      print(ctx.config:get_remote_service_repo_version_api())
      print(ctx.config:get_remote_service_repo_dir())
      print(ctx.config:get_remote_service_repo_latest_version_api())
      print(ctx.config:is_open_remote_update())
      print(ctx.config:get_remote_repo_check_interval())
      print(ctx.config:is_start_as_daemon())
      print(ctx.config:get_http_max_call_timeout())
      print(ctx.config:is_open_rpc_stat())
      print(ctx.config:has_attribute('test.array'))
      print_r(ctx.config:get_array('test.array'))
      print_r(ctx.config:get_table('test.table'))
      print(ctx.config:get_string('test.string'))
      print(ctx.config:get_integer('test.integer'))
      print(ctx.config:get_number('test.integer'))
      ctx.config:add_reload_listener("test",on_reload)
      return true
    end
    function on_before_destroy()
      ctx.config:remove_reload_listener("test")
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(100, sb1);
  sb1.stop();
}

CASE(TestLuaRedis1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    call = false
    function on_tick(now)
      if not call then
        call = true
        print(ctx.redis:bool_result("test", "SET k value", 1000))
        print(ctx.redis:string_result("test", "GET k", 1000))
        print(ctx.redis:bool_result("test", "SET k1 1", 1000))
        print(ctx.redis:integer_result("test", "GET k1", 1000))
        print(ctx.redis:bool_result("test", "EXISTS k", 1000))
        print(ctx.redis:bool_result("test", "EXISTS jjjjjj", 1000))
        print(ctx.redis:bool_result("test", "SADD sk 1 2 3", 1000))
        print_r(ctx.redis:array_result("test", "SMEMBERS sk", 1000))
        print(ctx.redis:bool_result("test", "HSET hk k v", 1000))
        print_r(ctx.redis:table_result("test", "HGETALL hk", 1000))
        print(ctx.redis:bool_result("test", "SET d 1.01", 1000))
        print(ctx.redis:number_result("test", "GET d", 1000))
      end
    end
    function on_after_fork()
      print("on_after_fork")
      ctx.redis:start()
      ctx.redis:add_host("test", "127.0.0.1", 6379, "", "")
      return true
    end
    function on_before_destroy()
      ctx.redis:stop()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(1000, sb1);
  sb1.stop();
}

CASE(TestLuaTime1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    call = false
    function on_tick(now)
      if not call then
        call = true
        print(ctx.time:get_millionsecond())
        print(ctx.time:get_second())
        print(ctx.time:utc_diff_second())
        print(ctx.time:diff_days_now(ctx.time:get_second()))
        print(ctx.time:diff_days(ctx.time:get_second(), ctx.time:get_second()))
        print_r(ctx.time:get_date())
        -- %d/%d/%d %d:%d:%d
        print_r(ctx.time:date_from_string("2020/01/01 00:00:00"))
        print_r(ctx.time:data_from_time(ctx.time:get_second()))
        print(ctx.time:is_same_day(ctx.time:get_second(), ctx.time:get_second()))
        print(ctx.time:is_same_week(ctx.time:get_second(), ctx.time:get_second()))
        print(ctx.time:is_same_month(ctx.time:get_second(), ctx.time:get_second()))
      end
    end
    function on_after_fork()
      print("on_after_fork")
      ctx.redis:start()
      ctx.redis:add_host("test", "127.0.0.1", 6379, "", "")
      return true
    end
    function on_before_destroy()
      ctx.redis:stop()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(1000, sb1);
  sb1.stop();
}

CASE(TestLuaHttp1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function server_cb(request)
      print_r(request)
      -- 异步导致协程出让
      print(ctx.redis:bool_result("test", "SET k value", 1000))
      local response = {}
      response.content = "hello world"
      response.status_code = 200
      return response
    end
    function client_cb(response)
      print_r(response)
      ctx:sleep(100)
      print("wakeup")
    end
    call = false
    function on_tick(now)
      if not call then
        call = true
        ctx.http:wait_request("127.0.0.1", 8888, server_cb)
        ctx.http:wait_response("127.0.0.1", 8888, "/", "GET", {test="value"}, "hello", 1000, client_cb)
      end
    end
    function on_after_fork()
      print("on_after_fork")
      ctx.redis:start()
      ctx.redis:add_host("test", "127.0.0.1", 6379, "", "")
      return true
    end
    function on_before_destroy()
      ctx.redis:stop()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(2000, sb1);
  sb1.stop();
}

CASE(TestLuaHttp2) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function server_cb(request)
      print_r(request)
      -- 异步导致协程出让
      print(ctx.redis:bool_result("test", "SET k value", 1000))
      local response = {}
      response.content = "hello world"
      response.status_code = 200
      return response
    end
    function client_cb(response)
      print_r(response)
      assert(false)
      print("wakeup")
    end
    call = false
    function on_tick(now)
      if not call then
        call = true
        ctx.http:wait_request("127.0.0.1", 8888, server_cb)
        ctx.http:wait_response("127.0.0.1", 8888, "/", "GET", {test="value"}, "hello", 1000, client_cb)
      end
    end
    function on_after_fork()
      print("on_after_fork")
      ctx.redis:start()
      ctx.redis:add_host("test", "127.0.0.1", 6379, "", "")
      return true
    end
    function on_before_destroy()
      ctx.redis:stop()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(2000, sb1);
  sb1.stop();
}

CASE(TestLuaSwtich1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function refresh_cb()
      return "display_name", true, "i am a tips"
    end
    function change_cb(on_off)
      return true, ""
    end
    function on_tick(now)
    end
    function on_after_fork()
      local result = ctx.console:add_switch("name", "tips", refresh_cb, change_cb)
      print(result)
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(2000, sb1);
  sb1.stop();
}

CASE(TestLuaSelection1) {
  std::ofstream ofs;
  ofs.open("5871407833815435533_lua/script/main.lua",
           std::ios::out | std::ios::trunc);
  static const char *test_lua = R"(
    function refresh_cb()
      return "display_name", {"a", "b", "c"}, "i am a tips"
    end
    function change_cb(name)
      return true, ""
    end
    function on_tick(now)
    end
    function on_after_fork()
      local result = ctx.console:add_selection("name", "tips", refresh_cb, change_cb)
      print(result)
      return true
    end
    function on_before_destroy()
      print("on_before_destroy")
    end
  )";
  ofs << test_lua;
  ofs.close();

  kratos::service::ServiceBox sb1;
  const char *argv1[] = {"test", "--config=box1.cfg"};
  ASSERT_TRUE(sb1.start(2, argv1));
  std::string error;
  dynamic_cast<kratos::config::BoxConfigImpl&>(sb1.get_config()).reload("box1.cfg", error);
  update_for(2000, sb1);
  sb1.stop();
}

CASE(TestLuaDebug1) {
  std::ofstream ofs;
  ofs.open("debug.lua", std::ios::out | std::ios::trunc);
  static const char *test_lua =
      R"(global_var = "abc"
       function test()
         local var = 1
         local var1 = 2
         global_var = "efg"
         print("i'm break")
         print("i'm back")
       end
       function call_test()
         test()
       end
       )";
  ofs << test_lua;
  ofs.close();

  if (global_lua) {
    lua_close(global_lua);
  }
  global_lua = luaL_newstate();
  luaL_openlibs(global_lua);
  Debugger debugger(global_lua, "test");
  debugger.add_breakpoint("debug.lua", 6);
  auto error = luaL_dofile(global_lua, "debug.lua");
  ASSERT_TRUE(error == 0);
  auto thread = lua_newthread(global_lua);
  std::string error_string;
  ASSERT_TRUE(kratos::lua::LuaUtil::call_lua_function_no_ret(
      "call_test", thread, error_string));
  debugger.execute(error_string);
  ASSERT_TRUE(error_string.empty());
  kratos::util::remove_file("debug.lua");
  std::cout << debugger.get_stack().to_string() << std::endl;
}

CASE(TestLuaDebug2) {
  std::ofstream ofs;
  ofs.open("debug.lua", std::ios::out | std::ios::trunc);
  static const char *test_lua =
      R"(global_var = "abc"
       function test()
         local var = 1
         local var1 = 2
         local double = 1.0
         local str = "str"
         local table = {}
         global_var = "efg"
         print("i'm break")
         print("i'm back")
       end
       function call_test()
         test()
       end
       )";
  ofs << test_lua;
  ofs.close();

  if (global_lua) {
    lua_close(global_lua);
  }
  global_lua = luaL_newstate();
  luaL_openlibs(global_lua);
  Debugger debugger(global_lua, "test");
  debugger.add_breakpoint("debug.lua", 9);
  auto error = luaL_dofile(global_lua, "debug.lua");
  ASSERT_TRUE(error == 0);
  auto thread = lua_newthread(global_lua);
  std::string error_string;
  ASSERT_TRUE(kratos::lua::LuaUtil::call_lua_function_no_ret(
      "call_test", thread, error_string));
  debugger.step_in(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  debugger.step_in(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  ASSERT_TRUE(error_string.empty());
  kratos::util::remove_file("debug.lua");
}

CASE(TestLuaDebug3) {
  std::ofstream ofs;
  ofs.open("debug.lua", std::ios::out | std::ios::trunc);
  static const char *test_lua =
      R"(global_var = "abc"
       function test()
         local var = 1
         local var1 = 2
         local double = 1.0
         local str = "str"
         local table = {}
         global_var = "efg"
         step_over_func("i'm break")
         step_over_func("i'm back")
         print("end")
       end
       function call_test()
         test()
       end
       function step_over_func(s)
         print(s)
       end
       )";
  ofs << test_lua;
  ofs.close();

  if (global_lua) {
    lua_close(global_lua);
  }
  global_lua = luaL_newstate();
  luaL_openlibs(global_lua);
  Debugger debugger(global_lua, "test");
  debugger.add_breakpoint("debug.lua", 9);
  auto error = luaL_dofile(global_lua, "debug.lua");
  ASSERT_TRUE(error == 0);
  auto thread = lua_newthread(global_lua);
  std::string error_string;
  ASSERT_TRUE(kratos::lua::LuaUtil::call_lua_function_no_ret(
      "call_test", thread, error_string));
  debugger.step_over(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  debugger.step_over(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  ASSERT_TRUE(error_string.empty());
  debugger.step_over(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  ASSERT_TRUE(error_string.empty());
  kratos::util::remove_file("debug.lua");
}

CASE(TestLuaDebug4) {
  std::ofstream ofs;
  ofs.open("debug.lua", std::ios::out | std::ios::trunc);
  static const char *test_lua =
      R"(local global_var = "abc"
       function test()
         local var = 1
         local var1 = 2
         local double = 1.0
         local str = "str"
         local table = {}
         global_var = "efg"
         step_out_func("i'm break")
         print("end")
       end
       function call_test()
         test()
         print("return call_test")
       end
       function step_out_func(s)
         print(s)
       end
       )";
  ofs << test_lua;
  ofs.close();

  if (global_lua) {
    lua_close(global_lua);
  }
  global_lua = luaL_newstate();
  luaL_openlibs(global_lua);
  Debugger debugger(global_lua, "test");
  debugger.add_breakpoint("debug.lua", 9);
  auto error = luaL_dofile(global_lua, "debug.lua");
  ASSERT_TRUE(error == 0);
  auto thread = lua_newthread(global_lua);
  std::string error_string;
  ASSERT_TRUE(kratos::lua::LuaUtil::call_lua_function_no_ret(
      "call_test", thread, error_string));
  std::cout << debugger.get_stack().to_string() << std::endl;
  debugger.step_out(error_string);
  std::cout << debugger.get_stack().to_string() << std::endl;
  debugger.execute(error_string);
  kratos::util::remove_file("debug.lua");
}

TEARDOWN([]() {
  kratos::util::remove_file("test1.lua");
  kratos::util::remove_file("test2.lua");
  kratos::util::remove_file("test3.lua");
  kratos::util::remove_file("test4.lua");
  kratos::util::remove_file("test5.lua");
  kratos::util::remove_file("test6.lua");
  kratos::util::remove_file("test7.lua");
  kratos::util::remove_file("test8.lua");
  kratos::util::remove_file("test9.lua");
  kratos::util::remove_file("test10.lua");

  kratos::util::remove_file("5871407833380299500_lua/script/main.lua");
  kratos::util::remove_file(
      "5871407833380299500_lua/proto/example.service.proto");
  kratos::util::remove_file("5871407833380299500_lua/json/idl.service.json");
  kratos::util::remove_file("5871407833380299500_lua/stub/stub.lua");
  kratos::util::rm_empty_dir("5871407833380299500_lua/script");
  kratos::util::rm_empty_dir("5871407833380299500_lua/proto");
  kratos::util::rm_empty_dir("5871407833380299500_lua/json");
  kratos::util::rm_empty_dir("5871407833380299500_lua/stub");
  kratos::util::rm_empty_dir("5871407833380299500_lua");

  kratos::util::remove_file("5871407833815435533_lua/script/main.lua");
  kratos::util::remove_file(
      "5871407833815435533_lua/proto/example.service.proto");
  kratos::util::remove_file("5871407833815435533_lua/json/idl.service.json");
  kratos::util::remove_file("5871407833815435533_lua/stub/stub.lua");
  kratos::util::rm_empty_dir("5871407833815435533_lua/script");
  kratos::util::rm_empty_dir("5871407833815435533_lua/proto");
  kratos::util::rm_empty_dir("5871407833815435533_lua/json");
  kratos::util::rm_empty_dir("5871407833815435533_lua/stub");
  kratos::util::rm_empty_dir("5871407833815435533_lua");

  kratos::util::rm_empty_dir("lua_proxy");
  kratos::util::remove_file("box1.cfg");
  kratos::util::remove_file("box2.cfg");

  if (global_lua) {
    lua_close(global_lua);
  }
})

FIXTURE_END(test_lua_helper)
